let pi = 3.14159265

class camera =
object (this)
  val mutable moving = false
  val mutable movingand = false
  val mutable norotate = false
  val mutable speed = 5.;
  val mutable dx = pi /. 2.
  val mutable dy = 0.
  val mutable pos =  (0., 0., 0.) (* position de la camera *)
  val mutable camx = (1., 0., 0.) (* axe des x (lateral) *)
  val mutable camy = (0., 1., 0.) (* axe des y (haut/bas) *)
  val mutable camz = (0., 0. ,1.) (* axe des z (avant/arriere) *)
  val mutable mid_x = 512. (* milieu de la fenetre en x *)
  val mutable mid_y = 384. (* milieu de la fenetre en y *)
  val mutable mx = 0.
  val mutable my = 0.

(* Methodes de get/set *)
  method set_speed s = speed <- s
  method set_pos n_pos =
    this#moveToStop;
    pos <- n_pos
  method get_pos = pos
  method get_dir = this#add pos camz
  method get_dir_nopos = camz
  method set_dir n_dir =
    let (x, y, z) = this#norm (this#neg n_dir pos) in
      camz <- (x, y, z);
      camx <- this#vect camz camy;
      if x < 0. then dy <- atan (z /. x) +. pi
      else dy <- atan (z /. x);
      dx <- acos y
  method get_up = camy
  method set_mid (x, y) =
    mid_x <- (float_of_int x)/.2.;
    mid_y <- (float_of_int y)/.2.

(* Fonctions a appeler selon le clavier *)
  method keyup =
    if moving then
    this#moveToStop;
    this#movePos (this#moveView "z" "more")
  method keydown =
    if moving then
    this#moveToStop;
    this#movePos (this#moveView "z" "less")
  method keyleft =
    if moving then
    this#moveToStop;
    this#movePos (this#moveView "x" "more")
  method keyright =
    if moving then
    this#moveToStop;
    this#movePos (this#moveView "x" "less")

(* Fonctions a appeler pour la souris (si bouton presse) *)
  method mouse x y =
    if moving then
    this#moveToStop;
    this#changeView (x -. mx) (y -. my); my <- y; mx <- x
  method mousePassive x y = my <- y; mx <- x

(* Fonctions operatoires *)
  method private vect (u1, u2, u3) (v1, v2, v3) =
    ((u2*.v3 -. u3*.v2),(u3*.v1 -. u1*.v3),(u1*.v2 -. u2*.v1))
  method private time x (a, b, c) = (x*.a, x*.b, x*.c)
  method private add (a, b, c) (x, y, z) = (a+.x, b+.y, c+.z)
  method private neg (a, b, c) (x, y, z) = (a-.x, b-.y, c-.z)
  method private norm (a, b, c) =
    let n = sqrt ((a*.a) +. (b*.b) +. (c*.c)) in
      ((a /. n), (b /. n), (c /. n))

  method private specnorm (a,b,c) def =
    let n = sqrt ((a*.a) +. (b*.b) +. (c*.c)) in
      if n > 0.0000000005 then
      ((a /. n), (b /. n), (c /. n))
      else
        def

(* Change l'orientation du repere de la camera *)
  method private boucle =
    if dx < 0.001 then dx <- 0.001;
    if dy < -.pi*.2. then dy <- pi*.2.;
    if dx > pi-.0.001 then dx <- pi-.0.001;
    if dy > pi*.2. then dy <- -.pi*.2.;
  method changeView x y =
    dx <- dx -. (y) *.pi/.1500.;
    dy <- dy -. (x) *.pi/.1500.;
    this#boucle;
    camz <- ((sin dx) *. (cos dy), cos dx, (sin dx) *. (sin dy));
    camx <- this#vect camz camy

(* Retourne le vecteur du changement de la position de la camera *)
  method private moveView axe sens = match axe with
    | "x" when sens = "more" -> this#time speed (this#norm camx)
    | "x" when sens = "less" ->
	let (x, y, z) = this#time speed camx in (-.x, -.y, -.z)
    | "z" when sens = "more" -> this#time speed camz
    | "z" when sens = "less" ->
	let (x, y, z) = this#time speed camz in (-.x, -.y, -.z)
    | _ -> failwith "Error 2"

(* Change la position de la camera *)
  method private movePos vect =
    pos <- this#add pos vect

  val mutable movePos = (0., 0., 0.)
  val mutable moveVect = (0., 0., 0.)
  val mutable moveUpdateVect = (0., 0., 0.)
  val mutable dirPos = (0., 0., 0.)
  val mutable dirVect = (0., 0., 0.)
  val mutable dirUpdateVect = (0., 0., 0.)
  val mutable newcamz = (0., 0., 0.)
  method set_moving stat = moving <- stat
  method get_moving = moving

  method private compare (x, y, z) (dx, dy, dz) =
    let xdx = x -. dx
    and ydy = y -.dy
    and zdz = z-. dz
    in
       (xdx*.xdx +. ydy*.ydy +. zdz*.zdz) < 0.25
  method private compare2 (x, y, z) (dx, dy, dz) =
    let (cx,cy,cz) = (y*.dz-.z*.dy,z*.dx-.x*.dz,x*.dy-.y*.dx)
    in
      (cx*.cx+.cy*.cy+.cz*.cz) < 0.1
    (*
    let xdx = x -. dx
    and ydy = y -.dy
    and zdz = z-. dz
    in
    let mag = (xdx*.xdx +. ydy*.ydy +. zdz*.zdz) in
      mag < 0.01
     *)

  method moveToInit norot movepos movedir =
    moving <- true;
    movingand <- true;
    norotate <- norot;
    movePos <- movepos;
    moveVect <- this#neg movePos pos;
    let (mvx, mvy, mvz) = moveVect in
    let n = sqrt (mvx*.mvx +. mvy*.mvy +. mvz*.mvz) in
    let t = n /. 20. in
      moveUpdateVect <- this#time t (this#norm moveVect);
    dirPos <- movedir;
    dirVect <- this#neg dirPos pos;
    newcamz <- dirVect;(*this#neg dirVect camz;*)
    dirUpdateVect <- this#norm (this#neg dirVect camz);

  method moveToStop =
    moving <- false;
    movingand <- false;
    camz <- this#norm camz;
    let (x, y, z) = camz in
      if x < 0. then dy <- atan (z /. (x+.0.00000000001)) +. pi
      else dy <- atan (z /. (x+.0.000000000001));
      dx <- acos y;
      this#boucle
  method moveToUpdate =
    if movingand && not(norotate) &&
       not (this#compare2 (this#norm newcamz) (this#norm camz)) then
      begin
	camz <- this#add (this#time (1.) dirUpdateVect) camz;
      end
    else
      begin
        movingand <- false;
        if not (this#compare movePos pos) then
          pos <- this#add moveUpdateVect pos
        else
          begin
          pos <- movePos;
          this#moveToStop
          end;
        if not(norotate) then
          begin
            dirVect <- this#neg dirPos pos;
            newcamz <- dirVect;
            camz <- dirVect;
          end;
      end;
    if (camz = newcamz && pos = movePos) then
      this#moveToStop;
end
